//
//  Publishers+BeginWith.swift
//  GerritHermes
//
//  Created by J.Zhou on 2020/1/8.
//  Copyright © 2020 J.Zhou. All rights reserved.
//

import Combine

extension Publisher {
    func beginWith(value: Output) -> Publishers.BeginWith<Self> {
        return Publishers.BeginWith(upstream: self, value: value)
    }
}

extension Publishers {
    struct BeginWith<Upstream: Publisher> : Publisher {
        typealias Output = Upstream.Output
        typealias Failure = Upstream.Failure

        let upstream: Upstream

        let value: Upstream.Output

        init(upstream: Upstream, value: Upstream.Output) {
            self.upstream = upstream
            self.value = value
        }

        func receive<Downstream: Subscriber>(subscriber: Downstream) where Upstream.Output == Downstream.Input, Upstream.Failure == Downstream.Failure {
            upstream.subscribe(Inner(downstream: subscriber, value: value))
        }
    }
}

extension Publishers.BeginWith {

    private struct Inner<Downstream: Subscriber>
        : Subscriber,
          CustomStringConvertible,
          CustomReflectable,
          CustomPlaygroundDisplayConvertible
        where Downstream.Input == Output, Downstream.Failure == Upstream.Failure
    {
        typealias Input = Upstream.Output

        typealias Failure = Upstream.Failure

        private let downstream: Downstream

        private let value: Output

        let combineIdentifier = CombineIdentifier()

        fileprivate init(downstream: Downstream, value: Output) {
            self.downstream = downstream
            self.value = value
        }

        func receive(subscription: Subscription) {
            subscription.request(downstream.receive(value))
            downstream.receive(subscription: subscription)
        }

        func receive(_ input: Input) -> Subscribers.Demand {
            return downstream.receive(input)
        }

        func receive(completion: Subscribers.Completion<Failure>) {
            downstream.receive(completion: completion)
        }

        var description: String { return "BeginWith" }

        var customMirror: Mirror {
            return Mirror(self, children: EmptyCollection())
        }

        var playgroundDescription: Any { return description }
    }
}
